﻿using System;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace SL.M.ManagedTest
{
    using Collections;

    [TestClass]
    public class DispatchTableTest
    {
        class DispatchProcess : Process
        {
            protected override void ProcessEntry()
            {
                throw new NotImplementedException();
            }
        }
        class DispatchProcess2 : DispatchProcess
        {
            protected override void ProcessEntry()
            {
                throw new NotImplementedException();
            }
        }
        class DispatchProcess3 : Process
        {
            protected override void ProcessEntry()
            {
                while (Started)
                {
                    NextMessage();
                }
            }
        }

        class DispatchTableProcess : Process
        {
            DispatchTable _table = new DispatchTable();
            public ManualResetEventSlim DoneEvent = new ManualResetEventSlim(false);
            string Message;
            bool Dispatched;

            protected override void ProcessEntry()
            {
                DispatchEntry de1 = DispatchEntry.Create<object>(DispatchSource.FromAnyProcess(), "us.sl.managedtest.f1", F1);
                DispatchEntry de2 = DispatchEntry.Create<object>(DispatchSource.FromProcess(this), "us.sl.managedtest.f2", F2);
                DispatchEntry de3 = DispatchEntry.Create<object>(DispatchSource.FromProcessOfType<DispatchTableProcess>(), "us.sl.managedtest.f3", F3);
                DispatchEntry de4 = DispatchEntry.Create<object>(DispatchSource.AggregateFromSources(de1.Key.Source, de2.Key.Source, de3.Key.Source), "us.sl.managedtest.f4", F4);

                Assert.AreEqual(0, _table.Count);
                Assert.AreEqual(false, _table.IsReadOnly);

                foreach (DispatchEntry entry in new DispatchEntry[] {  de1, de2, de3, de4 })
                {
                    Assert.IsFalse(_table.Contains(entry));
                    _table.Add(entry);
                    Assert.IsTrue(_table.Contains(entry));
                    Assert.AreEqual(1, _table.Count);
                    Assert.IsTrue(_table.Remove(entry));
                    Assert.IsFalse(_table.Remove(entry));
                    Assert.AreEqual(0, _table.Count);
                }

                foreach (DispatchEntry entry in new DispatchEntry[] { de1, de2, de3 })
                {
                    _table.Add(entry);
                }
                DispatchEntry[] copy = new DispatchEntry[_table.Count];
                _table.CopyTo(copy, 0);
                int idx = 0;
                foreach (DispatchEntry entry in _table)
                {
                    Assert.AreEqual(entry, copy[idx++]);
                }

                Assert.AreEqual(3, _table.Count);

                MessagePump("us.sl.managedtest.f1");
                Assert.AreEqual("F1", Message);
                Assert.IsTrue(Dispatched);

                MessagePump("us.sl.managedtest.f2");
                Assert.AreEqual("F2", Message);
                Assert.IsTrue(Dispatched);

                MessagePump("us.sl.managedtest.f3");
                Assert.AreEqual("F3", Message);
                Assert.IsTrue(Dispatched);

                _table.Clear();
                Assert.AreEqual(0, _table.Count);
                _table.Add(de4);
                Assert.AreEqual(1, _table.Count);

                MessagePump("us.sl.managedtest.f4");
                Assert.AreEqual("F4", Message);
                Assert.IsTrue(Dispatched);

                de4 = DispatchEntry.Create<object>(DispatchSource.AggregateFromSources(de2.Key.Source, de3.Key.Source), "us.sl.managedtest.f4", F4);
                MessagePump("us.sl.managedtest.f4");
                Assert.AreEqual("F4", Message);
                Assert.IsTrue(Dispatched);

                de4 = DispatchEntry.Create<object>(DispatchSource.AggregateFromSources(de3.Key.Source), "us.sl.managedtest.f4", F4);
                MessagePump("us.sl.managedtest.f4");
                Assert.AreEqual("F4", Message);
                Assert.IsTrue(Dispatched);

                DoneEvent.Set();
            }

            protected override bool HandleMessage(Message message)
            {
                if (_table.Dispatch(message))
                {
                    Dispatched = true;
                    return true;
                }
                return true;
            }

            void MessagePump(MessageType toSend)
            {
                Message = null;
                Dispatched = false;
                Send(toSend);

                while (Started && !Dispatched)
                {
                    NextMessage();
                }
            }

            bool F1(object a)
            {
                Message = "F1";
                return true;
            }

            bool F2(object a)
            {
                Message = "F2";
                return true;
            }

            bool F3(object a)
            {
                Message = "F3";
                return true;
            }

            bool F4(object a)
            {
                Message = "F4";
                return true;
            }
        }

        [TestMethod]
        public void ManagedTestDispatchKey()
        {
            DispatchProcess disp = new DispatchProcess();
            DispatchProcess2 disp2 = new DispatchProcess2();
            DispatchProcess3 disp3 = new DispatchProcess3();
            DispatchKey key = new DispatchKey(DispatchSource.FromAnyProcess(), "us.sl.managedtest.1");
            DispatchKey key2 = new DispatchKey(DispatchSource.FromProcess(disp), "us.sl.managedtest.2");
            DispatchKey key3 = new DispatchKey(DispatchSource.FromProcessOfType<DispatchProcess>(), "us.sl.managedtest.3");
            DispatchKey key4 = new DispatchKey(DispatchSource.AggregateFromSources(DispatchSource.FromProcess(disp), DispatchSource.FromProcess(disp2)), "us.sl.managedtest.4");
            DispatchKey key5 = new DispatchKey(DispatchSource.FromAnyProcess(), "us.sl.managedtest.1");
            Assert.AreEqual(key, key5);
            Assert.AreEqual(key.GetHashCode(), key5.GetHashCode());
            Assert.AreNotEqual(key, key2);
            Assert.AreNotEqual(key.GetHashCode(), key2.GetHashCode());
            Assert.AreEqual("us.sl.managedtest.1", key.Message);
            Assert.AreNotEqual("bar", key.Message);
            Assert.IsTrue(key.Source.Matches(disp));
            Assert.IsFalse(key2.Source.Matches(disp2));
            Assert.IsTrue(key2.Source.Matches(disp));
            Assert.IsFalse(key2.Source.Matches(disp2));
            Assert.IsTrue(key3.Source.Matches(disp));
            Assert.IsTrue(key3.Source.Matches(disp2));
            Assert.IsFalse(key3.Source.Matches(disp3));
            Assert.IsTrue(key4.Source.Matches(disp));
            Assert.IsTrue(key4.Source.Matches(disp2));
            Assert.IsFalse(key4.Source.Matches(disp3));
        }

        [TestMethod]
        public void ManagedTestDispatchEntry()
        {
            DispatchProcess disp = new DispatchProcess();
            DispatchProcess disp2 = new DispatchProcess();
            DispatchEntry e1 = DispatchEntry.Create<object>(DispatchSource.FromAnyProcess(), "us.sl.managedtest.1", (o) => true);
            DispatchEntry e2 = DispatchEntry.Create<object>(DispatchSource.FromProcess(disp), "us.sl.managedtest.2", (o) => true);
            DispatchEntry e3 = DispatchEntry.Create<object>(DispatchSource.FromAnyProcess(), "us.sl.managedtest.1", (o) => false);
            Assert.AreEqual(e1, e3);
            Assert.AreNotEqual(e1, e2);
            Assert.AreNotEqual(e2, e3);
            Assert.AreEqual(e1.Key, e3.Key);
            Assert.AreNotEqual(e1.Key, e2.Key);
        }

        [TestMethod]
        public void ManagedTestDispatchTable()
        {
            Game g = new Game();
            g.Start();
            DispatchTableProcess dt = new DispatchTableProcess();
            g.AddProcess(dt);
            dt.DoneEvent.Wait();
            g.Stop();
        }
    }
}
